/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.commons.utils;

import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.runtime.env.enums.StartupMode;
import io.iec.edp.caf.commons.spi.ApplicationContextResolver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;

import java.util.*;

/**
 * 应用上下文工具类
 *
 * @author wangyandong
 * @date 2019/9/20 15:13
 */
@Slf4j
public class SpringBeanUtils {

    private static Map<String, ApplicationContextResolver> applicationContextResolverMap = new HashMap();

    private static ApplicationContextResolver currentApplicationContextResolver = null;

    static {
        ServiceLoader<ApplicationContextResolver> resolvers = ServiceLoader.load(ApplicationContextResolver.class);
        for (ApplicationContextResolver resolver : resolvers) {
            applicationContextResolverMap.put(resolver.getId(), resolver);
        }

        if (CafEnvironment.getStartupMode() == StartupMode.Parallel) {
            currentApplicationContextResolver = applicationContextResolverMap.get("1");
        } else {
            currentApplicationContextResolver = applicationContextResolverMap.get("0");
        }

        Assert.notNull(currentApplicationContextResolver, "[CurrentApplicationContextResolver] is null, please check.");
    }

    /**
     * 设置应用上下文
     *
     * @param applicationContext 应用上下文
     */
    public static void setApplicationContext(ApplicationContext applicationContext) {
        if (log.isDebugEnabled()) {
            log.debug("CurrentApplicationContextResolver type [{}], ApplicationContext type [{}].", currentApplicationContextResolver.getClass().getName(), applicationContext != null ? applicationContext.getClass().getName() : null);
        }

        currentApplicationContextResolver.setApplicationContext(applicationContext);
    }

    /**
     * 获取应用上下文
     *
     * @return 应用上下文
     */
    public static ApplicationContext getApplicationContext() {
        ApplicationContext context = currentApplicationContextResolver != null ? currentApplicationContextResolver.getApplicationContext() : null;
        if (log.isDebugEnabled()) {
            log.debug("ApplicationContext type [{}].", context != null ? context.getClass().getName() : null);
        }

        return context;
    }

    /**
     * 根据模块名获取应用上下文
     * 因为运行时拿的getApplicationContext永远是platformcontext，所以业务模块里里的bean通过getBeansOfType会出现拿不到的情况（打了@Collect注解的除外）
     *
     * @param moduleName 模块名
     * @return 应用上下文
     */
    public static ApplicationContext getApplicationContextByModule(String moduleName) {
        ApplicationContext context = currentApplicationContextResolver != null ? currentApplicationContextResolver.getApplicationContext(moduleName) : null;
        if (log.isDebugEnabled()) {
            log.debug("ApplicationContext type [{}], module is [{}].", context != null ? context.getClass().getName() : null, moduleName);
        }

        return context;
    }

    /**
     * 根据名称获取Bean
     *
     * @param name Bean名称
     * @return Bean
     */
    public static Object getBean(String name) {
        ApplicationContext applicationContext = getApplicationContext();
        if (applicationContext != null)
            return applicationContext.getBean(name);
        return null;
    }

    /**
     * 根据类型获取Bean
     *
     * @param requiredType Bean类型
     * @return Bean
     */
    public static <T> T getBean(Class<T> requiredType) {
        ApplicationContext applicationContext = getApplicationContext();
        if (applicationContext != null)
            return applicationContext.getBean(requiredType);
        return null;
    }

    /**
     * 获取名称和类型获取Bean
     *
     * @param name         Bean名称
     * @param requiredType Bean类型
     * @return Bean
     */
    public static <T> T getBean(String name, Class<T> requiredType) {
        ApplicationContext applicationContext = getApplicationContext();
        if (applicationContext != null)
            return applicationContext.getBean(name, requiredType);
        return null;
    }

    /**
     * 根据名称和模块获取Bean(getBean也可以拿到，但理论上这个方法比getBean效率高)
     *
     * @param name       Bean名称
     * @param moduleName 模块名
     * @return Bean
     */
    public static Object getBeanByModule(String name, String moduleName) {
        ApplicationContext applicationContext = getApplicationContextByModule(moduleName);
        if (applicationContext != null)
            return applicationContext.getBean(name);
        return null;
    }

    /**
     * 根据类型和模块获取Bean(getBean也可以拿到，但理论上这个方法比getBean效率高)
     *
     * @param requiredType Bean类型
     * @param moduleName   模块名
     * @return Bean
     */
    public static <T> T getBeanByModule(Class<T> requiredType, String moduleName) {
        ApplicationContext applicationContext = getApplicationContextByModule(moduleName);
        if (applicationContext != null)
            return applicationContext.getBean(requiredType);
        return null;
    }

    /**
     * 根据名称、类型和模块获取Bean(getBean也可以拿到，但理论上这个方法比getBean效率高)
     *
     * @param name         Bean名称
     * @param requiredType Bean类型
     * @param moduleName   模块名
     * @return Bean
     */
    public static <T> T getBeanByModule(String name, Class<T> requiredType, String moduleName) {
        ApplicationContext applicationContext = getApplicationContextByModule(moduleName);
        if (applicationContext != null)
            return applicationContext.getBean(name, requiredType);
        return null;
    }

}
